/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.search.strategy;

import java.net.MalformedURLException;
import java.net.URL;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.object.MappingSqlQuery;

import net.groupme.exceptions.InsertFailedException;
import net.groupme.model.Group;
import net.groupme.model.MODEL_CONSTANTS;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.Tag;
import net.groupme.model.dao.DATABASE_CONSTANTS;
import net.groupme.search.strategy.model.WeightedIdentifiableComparatorByWeight;
import net.groupme.search.strategy.model.WeightedResource;
import net.groupme.search.strategy.model.WeightedTag;
import net.groupme.utils.sql.GetWeightedResource;

/**
 * created on Sep 23, 2007
 * 
 * This class performs search nearly like {@link SearchBasedOnGroupContext} but in a performance-gently
 * way.
 * Sequence:
 * <ol>
 * 	<li>search for directly tagged resources and compute score A</li>
 * 	<li>search for resources that are grouped in groups that are tagged directly and compute score B </li>
 *  <li>combine score A and B<li>
 * </ol>
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.7 $ $Date: 2008-05-08 13:37:56 $
 */
public class SearchBasedOnGroupContextPerformanceOptimized implements SearchStrategy{
	/** Logger for this class and subclasses */
	protected final  Log logger = LogFactory.getLog(this.getClass());

	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.SearchStrategy#searchForGroups(java.util.Collection, boolean)
	 */
	public List<Group> searchForGroups(Collection<Tag> tags, boolean allTags) {
		Collection<List<WeightedResource>> scoringAccordingToTag = new ArrayList<List<WeightedResource>>();
		for(Tag tag: tags){
			scoringAccordingToTag.add(searchForGroups(tag));
		}
		if(scoringAccordingToTag == null || scoringAccordingToTag.isEmpty()){
			return new ArrayList<Group>();
		}
		int numberOfTags = 0;
		//weight results (just make a sum):
		List<WeightedResource> overallScoring = new ArrayList<WeightedResource>();
		for(List<WeightedResource> weightedResources: scoringAccordingToTag){
			if(weightedResources != null){
				for(WeightedResource weightedResource: weightedResources){
					if(numberOfTags == 0){
						overallScoring.add(weightedResource);
					}else if(overallScoring.contains(weightedResource)){ //Note, that WeightedResource are compared, by their Resource and not by the weight-value!!!
						int pos = overallScoring.indexOf(weightedResource);
						overallScoring.get(pos).weight += weightedResource.weight;
					}else if(!allTags){
						overallScoring.add(weightedResource);
					}
				}
				numberOfTags++;
			}
		}
		Collections.sort(overallScoring, new WeightedIdentifiableComparatorByWeight());
		
		//put resources of WeightedResources into a list:
		List<Group> resultList = new ArrayList<Group>();
		for(int i=0; i<overallScoring.size(); i++){
			resultList.add(ObjectFactory.getGroup(overallScoring.get(i).resource.getId()));
		}
		return resultList;
	}

	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.SearchStrategy#searchForResources(java.util.Collection, boolean, boolean)
	 */
	public List<Resource> searchForResources(Collection<Tag> tags, boolean allTags, boolean noGroups) {
		Collection<List<WeightedResource>> scoringAccordingToTag = new ArrayList<List<WeightedResource>>();
		for(Tag tag: tags){
			scoringAccordingToTag.add(searchForResources(tag, noGroups));
		}
		if(scoringAccordingToTag == null || scoringAccordingToTag.isEmpty()){
			return new ArrayList<Resource>();
		}
		int numberOfTags = 0;
		//weight results (just make a sum):
		List<WeightedResource> overallScoring = new ArrayList<WeightedResource>();
		for(List<WeightedResource> weightedResources: scoringAccordingToTag){
			if(weightedResources != null){
			for(WeightedResource weightedResource: weightedResources){
				if(numberOfTags == 0){
					overallScoring.add(weightedResource);
				}else if(overallScoring.contains(weightedResource)){ //Note, that WeightedResource are compared, by their Resource and not by the weight-value!!!
					int pos = overallScoring.indexOf(weightedResource);
					overallScoring.get(pos).weight += weightedResource.weight;
				}else if(!allTags){
					overallScoring.add(weightedResource);
				}
			}
			
			}
			numberOfTags++;
		}
		
		Collections.sort(overallScoring, new WeightedIdentifiableComparatorByWeight());
		
		//put resources of WeightedResources into a list:
		List<Resource> resultList = new ArrayList<Resource>();
		for(int i=0; i<overallScoring.size(); i++){
			resultList.add(overallScoring.get(i).resource);
		}
		return resultList;
	}
	
	@SuppressWarnings("unchecked")
	protected List<WeightedResource> searchForResources(Tag tag, boolean noGroups){
		//Number of Users who tagged Resource X directly with tag:
		String query = 
			"SELECT tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ", " +
					"r." + DATABASE_CONSTANTS.RESOURCE_NAME + ", " +
 					"r." + DATABASE_CONSTANTS.RESOURCE_DESCRIPTION + ", " +
 					"r." + DATABASE_CONSTANTS.RESOURCE_URL + ", " +
 					"r." + DATABASE_CONSTANTS.RESOURCE_TYPE + ", " +
					"count(" + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ") " + 
				" FROM " + DATABASE_CONSTANTS.TABLE_USERTAGGROUPRESOURCE + " as tas, " + DATABASE_CONSTANTS.TABLE_RESOURCE + " as r " +
				" WHERE tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_TAGID + " = '" + tag.getId() + "'" +				
				(noGroups ? (" AND " + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " in " +
						"(SELECT " + DATABASE_CONSTANTS.RESOURCE_ID + " FROM " + DATABASE_CONSTANTS.TABLE_RESOURCE  + 
						" WHERE " + DATABASE_CONSTANTS.RESOURCE_TYPE + " != '" +  MODEL_CONSTANTS.RESOURCE_TYPE_GROUP + "')") : "") + 												
						" AND tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " = r." +  DATABASE_CONSTANTS.RESOURCE_ID +
				" group by tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + 
				" order by count(tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ") DESC";
		GetWeightedResource scoringQuery = new GetWeightedResource(ObjectFactory.ds, query);
		Iterator iter = scoringQuery.execute().iterator();
		List<WeightedResource> resources = null;
		if (iter.hasNext()){
			resources = (List<WeightedResource>) iter.next();
		}
		//add In-Memory-Tags:
		addTagsToInMemoryResources(resources);
		
		//resources contained in directly tagged groups:
		query = 
			"SELECT " + "gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + ", " +
			 			"r." + DATABASE_CONSTANTS.RESOURCE_NAME + ", " +
			 			"r." + DATABASE_CONSTANTS.RESOURCE_DESCRIPTION + ", " +
			 			"r." + DATABASE_CONSTANTS.RESOURCE_URL + ", " +
			 			"r." + DATABASE_CONSTANTS.RESOURCE_TYPE + ", " +
					"count(gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + ") " + 
				" FROM " + DATABASE_CONSTANTS.TABLE_GROUPRESOURCE + " as gr, " +
							DATABASE_CONSTANTS.TABLE_RESOURCE + " as r " +
				" WHERE gr." + DATABASE_CONSTANTS.GROUPRESOURCE_GROUP_ID + " in " +
					" ( SELECT distinct " + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " FROM " + DATABASE_CONSTANTS.TABLE_USERTAGGROUPRESOURCE + 
				" WHERE " + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_TAGID + " = '" + tag.getId() + "' AND " +
						    DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " in " +
							"(SELECT " + DATABASE_CONSTANTS.RESOURCE_ID + " FROM " + DATABASE_CONSTANTS.TABLE_RESOURCE  + 
								" WHERE " + DATABASE_CONSTANTS.RESOURCE_TYPE + " = '" +  MODEL_CONSTANTS.RESOURCE_TYPE_GROUP + "')" + 
								" group by " + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ") AND " +
						" gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + " = r." +  DATABASE_CONSTANTS.RESOURCE_ID +
				" group by gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + 
				" order by count(gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + ") DESC";
		scoringQuery = new GetWeightedResource(ObjectFactory.ds, query);
		iter = scoringQuery.execute().iterator();
		List<WeightedResource> groupResources = null;
		if (iter.hasNext()){
			groupResources = (List<WeightedResource>) iter.next();
		}
		//add In-Memory-Tags:
		addTagsToInMemoryResources(groupResources);
		
		//compute scoring:
		if(resources != null){
			if(groupResources != null){
				for(WeightedResource res: groupResources){
					if(resources.contains(res)){
						resources.get(resources.indexOf(res)).weight +=  ((SEARCH_STRATEGY_CONSTANTS.GROUP_CONTEXT_WEIGHT / SEARCH_STRATEGY_CONSTANTS.DIRECT_WEIGHT) * res.weight);
					}else{
						res.weight = ((SEARCH_STRATEGY_CONSTANTS.GROUP_CONTEXT_WEIGHT / SEARCH_STRATEGY_CONSTANTS.DIRECT_WEIGHT) * res.weight);
						resources.add(res);
					}
				}
			}
			Collections.sort(resources, new WeightedIdentifiableComparatorByWeight());
			return resources;
		}else{ //will never happen :)
			return groupResources;
		}
	}
	
	@SuppressWarnings("unchecked")
	protected List<WeightedResource> searchForGroups(Tag tag){
		//Number of Users who tagged Group X directly with tag:
		String query = 
			"SELECT tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ", " +
					"r." + DATABASE_CONSTANTS.RESOURCE_NAME + ", " +
 					"r." + DATABASE_CONSTANTS.RESOURCE_DESCRIPTION + ", " +
 					"r." + DATABASE_CONSTANTS.RESOURCE_URL + ", " +
 					"r." + DATABASE_CONSTANTS.RESOURCE_TYPE + ", " +
					"count(tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ") " + 
				" FROM " + DATABASE_CONSTANTS.TABLE_USERTAGGROUPRESOURCE + " as tas, " + DATABASE_CONSTANTS.TABLE_RESOURCE + " as r " +
				" WHERE tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_TAGID + " = '" + tag.getId() + "'" +
					" AND tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " in " +
						"(SELECT " + DATABASE_CONSTANTS.RESOURCE_ID + " FROM " + DATABASE_CONSTANTS.TABLE_RESOURCE  + 
						" WHERE " + DATABASE_CONSTANTS.RESOURCE_TYPE + " = '" +  MODEL_CONSTANTS.RESOURCE_TYPE_GROUP + "') " + 
					" AND tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " = r." +  DATABASE_CONSTANTS.RESOURCE_ID +
				" group by tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + 
				" order by count(tas." + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + ") DESC";
		GetWeightedResource scoringQuery = new GetWeightedResource(ObjectFactory.ds, query);
		Iterator iter = scoringQuery.execute().iterator();
		List<WeightedResource> groups = null;
		if (iter.hasNext()){
			groups = (List<WeightedResource>) iter.next();
		}
//		add In-Memory-Tags:
		addTagsToInMemoryResources(groups);
		
		//number of resources that are directly tagged with tag and contained in group X:
		  query = 
				"SELECT gr." + DATABASE_CONSTANTS.GROUPRESOURCE_GROUP_ID + ", " +
	 					"r." + DATABASE_CONSTANTS.RESOURCE_NAME + ", " +
	 					"r." + DATABASE_CONSTANTS.RESOURCE_DESCRIPTION + ", " +
	 					"r." + DATABASE_CONSTANTS.RESOURCE_URL + ", " +
	 					"r." + DATABASE_CONSTANTS.RESOURCE_TYPE + ", " +
						"count(" + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + ") " + 
					" FROM " + DATABASE_CONSTANTS.TABLE_GROUPRESOURCE + " as gr, " +
							DATABASE_CONSTANTS.TABLE_RESOURCE + " as r " +
					" WHERE gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + " in " +
							"(SELECT distinct " + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_RESOURCEID + " FROM " + DATABASE_CONSTANTS.TABLE_USERTAGGROUPRESOURCE  + 
							" WHERE " + DATABASE_CONSTANTS.USERTAGGROUPRESOURCE_TAGID + " = '" +  tag.getId() + "') " +
							" AND gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + " = r." +  DATABASE_CONSTANTS.RESOURCE_ID +
					" group by gr." + DATABASE_CONSTANTS.GROUPRESOURCE_GROUP_ID + 
					" order by count(gr." + DATABASE_CONSTANTS.GROUPRESOURCE_RESOURCE_ID + ") DESC";
			 scoringQuery = new GetWeightedResource(ObjectFactory.ds, query);
			iter = scoringQuery.execute().iterator();
			List<WeightedResource> groupsWithTaggedResources = null;
			if (iter.hasNext()){
				groupsWithTaggedResources = (List<WeightedResource>) iter.next();
			}
//			add In-Memory-Tags:
			addTagsToInMemoryResources(groupsWithTaggedResources);
			
			if(groups != null){
				if(groupsWithTaggedResources != null){
					for(WeightedResource res: groupsWithTaggedResources){
						if(groups.contains(res)){
							groups.get(groups.indexOf(res)).weight +=  ((SEARCH_STRATEGY_CONSTANTS.RESOURCES_IN_GROUP_WEIGHT / SEARCH_STRATEGY_CONSTANTS.DIRECT_WEIGHT) * res.weight);
						}else{
							res.weight = ((SEARCH_STRATEGY_CONSTANTS.RESOURCES_IN_GROUP_WEIGHT / SEARCH_STRATEGY_CONSTANTS.DIRECT_WEIGHT) * res.weight);
							groups.add(res);
						}
					}
				}
				Collections.sort(groups, new WeightedIdentifiableComparatorByWeight());
				return groups;
			}else{ //will never happen :)
				return groupsWithTaggedResources;
			}
	}

	private void addTagsToInMemoryResources(List<WeightedResource> resources){
		if(resources == null){
			return;
		}
		String query = "SELECT DISTINCT tas.resourceId, t.id, t.keyword, t.description  FROM userTagGroupResource as tas, Tag as t WHERE tas.tagId = t.id AND tas.resourceId in ( ";		
		for(int i=0; i < resources.size(); i++){
			query += resources.get(i).resource.getId();
			if(i != (resources.size() - 1)){
				query += ", ";
			}
		}
		query += ") order by tas.resourceId";
		Statement sqlStatement = null;
		ResultSet resourceIdAndTags = null;
		try {
			sqlStatement = ObjectFactory.ds.getConnection().createStatement();
			resourceIdAndTags = sqlStatement.executeQuery(query);
			int resPosition = -1;
			String lastResId = null;
			String actualResId = null;
			while(resourceIdAndTags.next()){
				actualResId = resourceIdAndTags.getString(1);
				if(actualResId != lastResId){//set Pointer (resPosition):
					resPosition = -1;
					for(int i=0; i < resources.size() && (resPosition == -1); i++){
						if(resources.get(i).resource.getId().equals(actualResId)){
							resPosition = i;
						}
					}
				}
				Tag tag = ObjectFactory.getInMemoryTag(resourceIdAndTags.getString(2), resourceIdAndTags.getString(3), resourceIdAndTags.getString(4));
				try {
					resources.get(resPosition).resource.addTag(null, tag);
				} catch (InsertFailedException e) {
					logger.error("Actually, we do not want to insert something in a DB. We just want to store the tag of the resource in-memory.");
				}
			}
		} catch (SQLException e) {
			logger.error("Unable to connect to DB while querying for Tag Cloud.", e);
		}
	}

	
}
